<!doctype html>
<html lang="en-us">
<head>

    <meta charset="utf-8">
    <meta name="generator" content="Hugo 0.59.1" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>springBoot&#43;Security&#43;JWT&#43;Jpa&#43;Vue实现登录权限控制 | 林贤钦</title>
    <meta property="og:title" content="springBoot&#43;Security&#43;JWT&#43;Jpa&#43;Vue实现登录权限控制 - 林贤钦">
    <meta property="og:type" content="article">
        
    <meta property="article:published_time" content="2020-03-24T13:26:34&#43;08:00">
        
        
    <meta property="article:modified_time" content="2020-03-24T13:26:34&#43;08:00">
        
    <meta name="Keywords" content="java,博客,项目管理,软件架构,公众号,小程序">
    <meta name="description" content="springBoot&#43;Security&#43;JWT&#43;Jpa&#43;Vue实现登录权限控制">
        
    <meta name="author" content="林贤钦">
    <meta property="og:url" content="http://linxianqin.gitee.io/post/%E6%B5%85%E8%B0%88springboot-Security-Jwt-%E5%AE%9E%E7%8E%B0token/">
    <link rel="shortcut icon" href="/img/favicon.ico" type="image/x-icon">

    <link rel="stylesheet" href="/css/normalize.css">
    
        <link rel="stylesheet" href="/css/prism.css">
    
    <link rel="stylesheet" href="/css/style.css">
    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

    


    
    
</head>

<body>
<header id="header" class="clearfix">
	<div class="container">
        <div class="col-group">
            <div class="site-name ">
                
                    <a id="logo" href="http://linxianqin.gitee.io">
                        林贤钦
                    </a>
                
                <p class="description">专注于Java、微信小程序、移动互联网、项目管理、软件架构</p>
            </div>
            <div>
                <nav id="nav-menu" class="clearfix">
                    <a class="current" href="http://linxianqin.gitee.io">首页</a>
                    
                    <a  href="http://linxianqin.gitee.io/archives/" title="归档">归档</a>
                    
                    <a  href="http://linxianqin.gitee.io/about/" title="关于">关于</a>
                    
                </nav>
            </div>
        </div>
    </div>
</header>


<div id="body">
        
        
    <div class="container">
        <div class="col-group">

            <div class="col-8" id="main">
                <div class="res-cons">
                    <article class="post">
                        <header>
                            <h1 class="post-title">springBoot&#43;Security&#43;JWT&#43;Jpa&#43;Vue实现登录权限控制</h1>
                        </header>
                        <date class="post-meta meta-date">
                            2020年3月24日
                        </date>
                        
                        <div class="post-meta">
                            <span>|</span>
                            
                                <span class="meta-category"><a href="http://linxianqin.gitee.io/categories/springSecurity">springSecurity</a></span>
                            
                                <span class="meta-category"><a href="http://linxianqin.gitee.io/categories/JWT">JWT</a></span>
                            
                        </div>
                        
                        
                        <div class="post-meta">
                            <span id="busuanzi_container_page_pv">|<span id="busuanzi_value_page_pv"></span><span> 阅读</span></span>
                        </div>
                        
                        
                        <div class="post-content">
							<nav id="TableOfContents">
<ul>
<li>
<ul>
<li><a href="#一-springboot-security-集合jwt的后端实现">一、springboot security 集合jwt的后端实现</a>
<ul>
<li><a href="#需求">需求</a></li>
<li><a href="#准备工作">准备工作</a></li>
</ul></li>
<li><a href="#二-说明">二、说明</a>
<ul>
<li><a href="#三-实现自定义登录验证结果处理配置">三、实现自定义登录验证结果处理配置</a></li>
<li><a href="#security-自定义配置类">Security 自定义配置类</a></li>
</ul></li>
</ul></li>
</ul>
</nav>
                            

<h2 id="一-springboot-security-集合jwt的后端实现">一、springboot security 集合jwt的后端实现</h2>

<p>Spring Security 是一种基于spring AOP 和servlet的过滤器的安全框架，它提供了全面的安全性解决方案，同时在web请求级和方法调用级处理身份确认和授权(简单理解就是通过filter拦截http请求实现权限控制)</p>

<p>JSON Web Token(JWT ) 是目前最流行的跨域身份验证解决方案。其原理是将用户信息的Json字符串加密生成唯一的token返回前端，后端通过解析token来验证是否已登录</p>

<p>SpringData JPA 是spring基于ORM框架、JPA规范的基础上封装的一套JPA应用框架、可以使开发者使用极简的代码实现对数据库的访问和操作。</p>

<h3 id="需求">需求</h3>

<ul>
<li>包含多个角色，每个角色所具有的权限不同(可以看到不同的菜单和页面)</li>
<li>对整个系统做登录控制，即未登录访问则返回json数据提示</li>
<li>对于没有权限的访问，返回json数据提示</li>
<li>实现登出功能</li>
</ul>

<h3 id="准备工作">准备工作</h3>

<ul>
<li>添加配置pom.xml</li>
<li>springboot、security的其他相关配置</li>
<li>User类(相关的Bean)</li>
<li>数据访问层：UserRepository</li>
<li>jwt工具类(文章末尾)</li>
<li>数据库设计5张表(User用户表、AccountRoleRelation用户角色表、Role角色表、RolePermissionRelation角色权限表、Permission权限表)</li>
</ul>

<h2 id="二-说明">二、说明</h2>

<ul>
<li>自定义UserDetail：构建Authentication对象必须的信息，可以自定义</li>

<li><p>自定义UserDetailService：通过username构建UserDetail对象，通过loadUserByUsername 根据userName获取UserDetail对象</p></li>

<li><p>自定义请求鉴权器：JwtAuthenticationTokenFilter 、拦截所有请求，从cookie中查询token信息，并进行解析鉴定</p></li>

<li><p>自定义登录认证器：CustomAuthenticationProvider 、自定义登录认证器、负责将登录过滤器拦截得到的登录账户密码进行验证</p></li>

<li><p>自定义动态加载权限url：MyRBACService</p></li>

<li><p>登录成功Handler：MyAuthenticationSuccessHandler、自定义登录成功返回消息类、 实现cookie的存储</p></li>

<li><p>鉴权失败Handler：MyAuthenticationFailureHandler、自定义登录失败返回消息类：直接返回登录失败信息</p></li>

<li><p>自定义登出Handler： MyLogoutSuccessHandler 、自定义退出功能业务处理类</p></li>

<li><p>自定义无权限访问 : MyAccessDeniedHandler、自定义无权限返回json消息</p></li>

<li><p>自定义未登录无权访问资源：MyAuthenticationEntryPoint、匿名用户访问无权限资源的异常返回</p></li>

<li><p>Security 自定义配置类</p></li>
</ul>

<h3 id="三-实现自定义登录验证结果处理配置">三、实现自定义登录验证结果处理配置</h3>

<p>1、自定义UserDetail：构建Authentication对象必须的信息，可以自定义</p>

<pre><code class="language-java">@Data
public class MyUserDetails implements UserDetails {
    private Long userId;//用户id
    private String nikName; //用户昵称
    private String headSculptureUrl;//用户头像url
    private String username;  //用户名
    private String password; //密码
    private boolean accountNonExpired;   //是否没过期
    private boolean accountNonLocked;   //是否没被锁定
    private boolean credentialsNonExpired;  //是否没过期
    private boolean enabled;  //账号是否可用
    Collection&lt;? extends GrantedAuthority&gt; authorities;  //用户的权限集合

    @Override
    public Collection&lt;? extends GrantedAuthority&gt; getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

}
</code></pre>

<p>2、自定义UserDetailService：通过username构建UserDetail对象，通过loadUserByUsername 根据userName获取UserDetail对象</p>

<pre><code class="language-java">@Component
public class MyUserDetailService  implements UserDetailsService  {
    @Resource
    private UserRepository userRepository;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user =userRepository.findByAccount(username);
        if(user == null){
            throw new UsernameNotFoundException(&quot;用户名 :&quot; + username + &quot;没找到&quot;);
        }
        //加载用户角色列表
        Set&lt;Role&gt; roles = user.getRoles();
        List&lt;String&gt; roleCodes= roles.stream().map(Role::getRoleCode).collect(Collectors.toList());
        //角色是一个特殊的权限，ROLE_前缀
        List&lt;String&gt; authorties = roleCodes.stream().map(rc -&gt; &quot;ROLE_&quot; +rc).collect(Collectors.toList());
        //通过用户角色列表加载用户的资源权限列表
        List&lt;Set&lt;Permission&gt;&gt; collect = roles.stream().map(Role::getPermissions).collect(Collectors.toList());
        for (Set&lt;Permission&gt; permissions : collect) {
            List&lt;String&gt; url=permissions.stream().map(Permission::getUrl).collect(Collectors.toList());
            authorties.addAll(url);
        }
        MyUserDetails myUserDetails = new MyUserDetails();
        myUserDetails.setUserId(user.getId());
        myUserDetails.setUsername(user.getAccount());
        myUserDetails.setPassword(user.getPassword());
        myUserDetails.setNikName(user.getNickname());
        myUserDetails.setHeadSculptureUrl(user.getHeadSculptureUrl());
        if (user.getIsDelete()== IsDeleteEnum.UNDELETED.getValue()){
            myUserDetails.setEnabled(true);
        }
        //设置权限到myUserDetails的authorties
        myUserDetails.setAuthorities( AuthorityUtils.commaSeparatedStringToAuthorityList(
                String.join(&quot;,&quot;,authorties)
        ));
        return myUserDetails;
    }
}
</code></pre>

<p>3、请求鉴权器：JwtAuthenticationTokenFilter 拦截所有请求，从cookie中查询token信息，并进行解析鉴定</p>

<pre><code class="language-java">@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    private final static Logger logger = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);

    @Resource
    private JwtTokenUtils jwtTokenUtils;

    @Autowired
    private  MyUserDetailService myUserDetailService;


    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain chain) throws ServletException, IOException {
        Cookie[] cookies = request.getCookies();
        String authorization = &quot;&quot;;
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (jwtTokenUtils.tokenHeader.equals(cookie.getName())) {
                    authorization = cookie.getValue();
                }
            }
        }
        if (&quot;&quot;.equals(authorization)) {
            chain.doFilter(request, response);
            return;
        }
        UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(authorization);
        if (authenticationToken == null) {
            chain.doFilter(request, response);
            return;
        }
        //设置用户登录状态
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        chain.doFilter(request, response);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(String authorization) {
        //解析token
        try {
            if (StringUtils.isNotBlank(authorization)) {
                if (StringUtils.isNotBlank(authorization) &amp;&amp; authorization.startsWith(jwtTokenUtils.tokenHead)) {
                    //如果header中存在token，则覆盖掉url中的token
                    authorization = authorization.substring(jwtTokenUtils.tokenHead.length()); // &quot;Bearer &quot;之后的内容
                }
                String username = jwtTokenUtils.getUsernameFromToken(authorization);
                logger.info(&quot;checking authentication {}&quot;, username);
                if (username != null &amp;&amp; SecurityContextHolder.getContext().getAuthentication() == null) {
                    UserDetails userDetails = myUserDetailService.loadUserByUsername(username);
                    //检查token是否有效
                    if (jwtTokenUtils.validateToken(authorization, userDetails)) {
                        logger.info(&quot;token有效&quot;);
                        return  new UsernamePasswordAuthenticationToken(
                                userDetails,userDetails.getPassword(),userDetails.getAuthorities()
                        );
                    }
                    logger.error(&quot;token无效&quot;);
                }
            }
        } catch (MalformedJwtException | ExpiredJwtException e) {
            e.printStackTrace();
            return null;
        }
        return null;
    }
}
</code></pre>

<p>4、CustomAuthenticationProvider：自定义登录认证器、负责将登录过滤器拦截得到的登录账户密码进行验证</p>

<pre><code class="language-java">public class CustomAuthenticationProvider implements AuthenticationProvider {
    private final  MyUserDetailService myUserDetailService;
    public CustomAuthenticationProvider( MyUserDetailService myUserDetailService){
        this.myUserDetailService=myUserDetailService;
    }
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        UsernamePasswordAuthenticationToken token =(UsernamePasswordAuthenticationToken)authentication;
        String username = token.getName();
        UserDetails userDetails=null;
        if (username !=null){
            //调用相应的service从数据库中获取对应的用户信息
            userDetails = myUserDetailService.loadUserByUsername(username);
        }
        if (userDetails==null){
            throw new UsernameNotFoundException(&quot;用户名或密码无效&quot;);
        }else if (!userDetails.isEnabled()){
            throw new DisabledException(&quot;用户已被禁用&quot;);
        }else if(!userDetails.isAccountNonExpired()){
            throw new AccountExpiredException(&quot;账号已过期&quot;);
        }else if (!userDetails.isAccountNonLocked()){
            throw new LockedException(&quot;账户已被锁定&quot;);
        }else if (!userDetails.isCredentialsNonExpired()){
            throw new LockedException(&quot;凭证已过期&quot;);
        }
        //数据库中的密码
        String password = userDetails.getPassword();
        //token中密码
        String tokenPassword=(String)token.getCredentials();
        //密码验证
        if(!BCrypt.checkpw(tokenPassword,password)){
            throw  new BadCredentialsException(&quot;用户名或密码错误&quot;);
        }
        return new UsernamePasswordAuthenticationToken(userDetails,password,userDetails.getAuthorities());
    }

    @Override
    public boolean supports(Class&lt;?&gt; authentication) {
        return UsernamePasswordAuthenticationToken.class.equals(authentication);
    }
}
</code></pre>

<p>5、MyRBACService：动态加载权限url</p>

<pre><code class="language-java">@Component(&quot;rabcService&quot;)
public class MyRBACService {
    private AntPathMatcher antPathMatcher = new AntPathMatcher();
    /**
     * 判断某用户是否具有该request资源的访问权限
     */
    public boolean hasPermission(HttpServletRequest request , Authentication authentication){
        Object principal = authentication.getPrincipal();
        if ((principal instanceof UserDetails)){
            UserDetails userDetails =((UserDetails)principal);
            List&lt;GrantedAuthority&gt; authorities =
                    AuthorityUtils.commaSeparatedStringToAuthorityList(request.getRequestURI());
            return userDetails.getAuthorities().contains(authorities.get(0));
        }
        return false;
    }
}
</code></pre>

<p>6、MyAuthenticationSuccessHandler ：自定义登录成功返回消息类、 实现cookie的存储</p>

<pre><code class="language-java">@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    @Resource
    private UserDetailsService userDetailsService;
    @Resource
    private AdminService adminService;
    @Value(&quot;${spring.security.loginType}&quot;)
    private String loginType;
    @Resource
    private JwtTokenUtils jwtTokenUtils;
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request,
                                        HttpServletResponse response,
                                        Authentication authentication)
            throws IOException, ServletException {

        response.setContentType(&quot;application/json;charset=utf-8&quot;);
        MyUserDetails userDetails = (MyUserDetails)authentication.getPrincipal();
        //登录的相关业务，token的生成
        final String token = adminService.login(userDetails);
        //将token和用户信息发送到前端
        if (loginType.equalsIgnoreCase(&quot;JSON&quot;)&amp;&amp;token!=null){
            //返回登录成功信息
            response.addCookie(new Cookie(jwtTokenUtils.tokenHeader, jwtTokenUtils.tokenHead + token));
            response.getWriter().write(JsonUtil.objectToJson(BaseResponse.ok(&quot;登录成功&quot;)));
        }else{
            //返回登录失败信息
            logger.debug(userDetails+&quot;MyAuthenticationSuccessHandler登录失败&quot;);
            response.getWriter().write(JsonUtil.objectToJson(new BaseResponse(400,&quot;请检查用户名、密码或验证码是否正确&quot;)));
        }
    }
}
</code></pre>

<p>7、MyAuthenticationFailureHandler、自定义登录失败返回消息类：直接返回登录失败信息</p>

<pre><code class="language-java">@Component
public class MyAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response,
                                        AuthenticationException exception)
            throws IOException, ServletException {
        //返回登录失败信息
        response.setContentType(&quot;application/json;charset=utf-8&quot;);
        //返回登录失败信息
        response.getWriter().write(JsonUtil.objectToJson(new BaseResponse(404,&quot;请检查用户名、密码或验证码是否正确&quot;)));
    }

}
</code></pre>

<p>8、MyLogoutSuccessHandler：自定义退出功能业务处理类</p>

<pre><code class="language-java">@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
    protected Logger logger = LoggerFactory.getLogger(MyLogoutSuccessHandler.class);
    @Resource
    private AdminService adminService;
    @Resource
    private UserService userService;
    @Override
    public void onLogoutSuccess(HttpServletRequest request,
                                HttpServletResponse response,
                                Authentication authentication)
            throws IOException, ServletException {
        response.setContentType(&quot;application/json;charset=utf-8&quot;);
        adminService.logout(userService.getUserIdByThread());//退出的相关业务实现
        //返回退出成功信息
        response.getWriter().write(JsonUtil.objectToJson(BaseResponse.ok(&quot;登出成功&quot;)));
    }
}
</code></pre>

<p>9、MyAccessDeniedHandler、自定义无权限</p>

<pre><code class="language-java">@Component
public class MyAccessDeniedHandlerImplements implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.setContentType(&quot;application/json;charset=utf-8&quot;);
        response.getWriter().write(JsonUtil.objectToJson(new BaseResponse(403,&quot;无权限访问此内容&quot;)));
    }
}
</code></pre>

<p>10、MyAuthenticationEntryPoint：匿名用户访问无权限资源的异常返回</p>

<pre><code class="language-java">@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.setContentType(&quot;application/json;charset=utf-8&quot;);
        response.getWriter().write(JsonUtil.objectToJson(
                new BaseResponse(401,&quot;未登录。请重新登录&quot;)));
    }
}
</code></pre>

<h3 id="security-自定义配置类">Security 自定义配置类</h3>

<pre><code class="language-java">@Configuration
@EnableWebSecurity// 这个注解必须加，开启Security
@EnableGlobalMethodSecurity(prePostEnabled = true)//保证post之前的注解可以使用
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 登录成功自定义业务处理类
     */
    @Resource
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    /**
     * 登录失败自定义业务处理类
     */
    @Resource
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;

    /**
     * 退出成功自定义业务处理类
     */
    @Resource
    private MyLogoutSuccessHandler myLogoutSuccessHandler;

    /**
     * 自定义无权限访问处理 解决认证过的用户访问无权限资源的异常
     */
    @Resource
    private MyAccessDeniedHandler myAccessDeniedHandler;
    /**
     * 自定义无权限访问处理 解决认证过的用户访问无权限资源的异常
     */
    @Resource
    private MyAuthenticationEntryPoint myAuthenticationEntryPoint;

    @Resource
    private MyUserDetailService myUserDetailService;

    //先来这里认证一下
    @Override
    protected void configure(AuthenticationManagerBuilder auth)throws Exception{
        auth.authenticationProvider(authenticationProvider());
    }
    
    @Bean
    public AuthenticationProvider authenticationProvider(){
        AuthenticationProvider authenticationProvider = new CustomAuthenticationProvider(myUserDetailService);
        return authenticationProvider;
    }
    
    //jwt
    @Bean
    public JwtAuthenticationTokenFilter authenticationTokenFilterBean()
        throws Exception{
        return new JwtAuthenticationTokenFilter();
    }
	//密码加密
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    
	//拦截在这配
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .cors().and()//应该是允许跨域
            //登出相关配置
            .logout().permitAll()
                    .logoutUrl(&quot;/api/logout&quot;) //指定退出登录url(方法中设置销毁session等逻辑)
                .logoutSuccessHandler(myLogoutSuccessHandler)//自定义登录失败返回数据，否正会默认跳转登录页面
                //.invalidateHttpSession(true)//定义登出时是否invalidate HttpSession
                .deleteCookies(JwtTokenUtils.tokenHeader)//在登出同时清除cookies

            //登录表单相关配置
            //.and().formLogin()//使用formLogin登录模式
               // .loginPage(&quot;/login.html&quot;)
            .and().csrf().disable()
                .formLogin()
                .usernameParameter(&quot;username&quot;)
                .passwordParameter(&quot;password&quot;)
                .successHandler(myAuthenticationSuccessHandler)//自定义登录成功返回数据，反则会默认跳转到成功页面
                .failureHandler(myAuthenticationFailureHandler)//自定义登录失败返回数据，反则会默认跳转到error页面
                .loginProcessingUrl(&quot;/api/login&quot;).permitAll()//这里配置的loginProcessingUrl为页面中对应表单的 action ，该请求为 post，并设置可匿名访问
            //配置需要认证的请求
                .and().authorizeRequests()
                // 对登录注册要允许匿名访问;
                .antMatchers(&quot;/login.html&quot;, &quot;/api/login&quot;).permitAll()
                .antMatchers(AUTH_WHITELIST).permitAll()//swagger-ui 释放接口
                //.antMatchers(&quot;/**/*&quot;).denyAll()
                .antMatchers(&quot;/index&quot;).permitAll()//首页不用登录
                .antMatchers(&quot;/api/userRegister/**&quot;).permitAll()//注册页面放出
                //以“/admin”开头的url，并需拥有ROLE_admin角色权限，这里用hasRole不需要写“ROLE_前缀
               .antMatchers(&quot;/api/admin/**&quot;).hasRole(&quot;ADMIN&quot;)
               .antMatchers(&quot;/api/teacher/**&quot;).hasRole(&quot;TEACHER&quot;)
               .antMatchers(&quot;/api/common/**&quot;).hasRole(&quot;COMMON&quot;)
               .antMatchers(&quot;/api/app/**&quot;).authenticated()//需要携带有效token
                 //其余任何方法需要RBAC过滤URL
               .anyRequest().access(&quot;@rabcService.hasPermission(request,authentication)&quot;)
               .and().exceptionHandling()//配置被拦截时的处理，这位置很关键？
               .authenticationEntryPoint(myAuthenticationEntryPoint)//未登录访问 返回json
               .accessDeniedHandler(myAccessDeniedHandler)//添加无权限时的处理
            //基于token，所以不需要session
           .and().sessionManagement()
               .sessionCreationPolicy(SessionCreationPolicy.STATELESS)//关闭session
               .sessionFixation().none()
            ;
        // 添加JWT filter
        http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);

    }
 
}
</code></pre>

<p>最后补充细节</p>

<p>1、JwtTokenUtils工具类</p>

<pre><code class="language-java">@Slf4j
@Component
public class JwtTokenUtils {
    private static final String CLAIM_KEY_USERNAME = &quot;sub&quot;;
    private static final String CLAIM_KEY_ID = &quot;id&quot;;
    private static final String CLAIM_KEY_CREATED = &quot;created&quot;;
    private static final String CLAIM_KEY_ROLES = &quot;roles&quot;;
    private static final String CLAIM_KEY_SECRET = &quot;11111111&quot;;
    public static final long CLAIM_KEY_EXPIRATION = 1000 * 24 * 60 * 60 * 7;
    public static final String tokenHeader = &quot;Authorization&quot;;
    public static final String tokenHead = &quot;Bearer:&quot;;

    /**
     * 从令牌中获取用户名
     * @param token
     * @return
     */
    public String getUsernameFromToken(String token) {
        String username;
        try {
            username =getClaimsFromToken(token).getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 从令牌中获取userId
     * @param token
     * @return
     */
    public Long getUserIdFromToken(String token){
        Long userId;
        try{
            Claims claims = getClaimsFromToken(token);
            userId = (Long) claims.get((CLAIM_KEY_ID));
        }catch(Exception e){
            userId=null;
        }
        return userId;
    }

    /**
     * 获取令牌密令
     * @param token
     * @return
     */
    public Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token);
            created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
        } catch (Exception e) {
            created = null;
        }
        return created;
    }

    /**
     * 获取令牌有效时间
     * @param token
     * @return
     */
    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    /**
     * 从令牌中获取数据说明
     * @param token
     * @return
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(CLAIM_KEY_SECRET).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + CLAIM_KEY_EXPIRATION * 1000);
    }


    /**
     * 生产令牌
     * @param userDetails 用户
     * @return 令牌
     */
    public String generateToken(MyUserDetails userDetails) {
        Map&lt;String, Object&gt; claims = new HashMap&lt;&gt;();
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED, new Date());
        claims.put(CLAIM_KEY_ID, userDetails.getUserId());
        claims.put(CLAIM_KEY_ROLES, userDetails.getAuthorities());
        return generateToken(claims);
    }

    /**
     * 从数据声明生产令牌
     * @param claims
     * @return
     */
    public String generateToken(Map&lt;String, Object&gt; claims) {
        Date expirationDate = new Date(System.currentTimeMillis() + CLAIM_KEY_EXPIRATION);
        return Jwts.builder().setClaims(claims)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, CLAIM_KEY_SECRET)
                .compact();
    }

    /**
     * 判断是否可以刷新令牌
     * @param token
     * @return
     */
    public Boolean canTokenBeRefreshed(String token) {
        return !isTokenExpired(token);
    }

    /**
     *  判断令牌是否过期
     * @param token
     * @return
     */
    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }
    /**
     * 刷新令牌
     * @param token 原令牌
     * @return
     */
    public String refreshToken(String token) {
        String refreshedToken;
        try {
            final Claims claims = getClaimsFromToken(token);
            claims.put(CLAIM_KEY_CREATED, new Date());
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    /**
     * 验证令牌
     * @param token
     * @param userDetails
     * @return
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        MyUserDetails myUserDetails =(MyUserDetails) userDetails;
        final String username = getUsernameFromToken(token);
        final Date created = getCreatedDateFromToken(token);
        return (
                username.equals(myUserDetails.getUsername())
                        &amp;&amp; isTokenExpired(token)==false);
    }
}
</code></pre>

<p>2、user类</p>

<pre><code class="language-java">@Data
@Entity
@Table(name = &quot;tb_account&quot;)
@ToString
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
public class User extends BaseEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;)
    private Long id;

    @Column(name = &quot;acc_nickname&quot;)
    private String nickname;

    @Column(name = &quot;acc_account&quot;)
    private String account;

    @Column(name = &quot;acc_head_sculpture_url&quot;)
    private String headSculptureUrl;

    @Column(name = &quot;acc_password&quot;)
    private String password; 
    
    @Column(name = &quot;is_delete&quot;)
    private Byte isDelete;

    @ManyToMany(fetch = FetchType.EAGER, targetEntity = Role.class)
    @JoinTable(name = &quot;tb_account_role_relation&quot;,joinColumns = @JoinColumn(name = &quot;account_id&quot;),inverseJoinColumns = @JoinColumn(name = &quot;role_id&quot;))
    private Set&lt;Role&gt; roles = new LinkedHashSet&lt;&gt;();
}
</code></pre>

<p>3、数据访问层：</p>

<pre><code class="language-java">public interface UserRepository extends BaseRepository&lt;User,Long&gt;, JpaSpecificationExecutor&lt;User&gt; {

    User findByAccount(String account);
}
</code></pre>

<p>4、登录的业务处理方法：</p>

<pre><code>@Override
    public String login(MyUserDetails myUserDetails) {
        Assert.notNull(myUserDetails,&quot;myUserDetails must not be null&quot;);
        try {
            User user= new User();
            user.setAccount(myUserDetails.getUsername());
            user.setId(myUserDetails.getUserId());
            user.setNickname(myUserDetails.getNikName());
            user.setHeadSculptureUrl(myUserDetails.getHeadSculptureUrl());
            //将用户信息放入threadLocal中,线程共享
            setUserByThread(user);
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
        //返回token
        return buildAuthToken(myUserDetails);
    }
</code></pre>

<p>5、登出的业务处理方法</p>

<pre><code class="language-java">@Override
public void logout(@NonNull Long userId) {
    //写一些业务逻辑，比如：登录时间的统计
    //登出后清除用户缓存信息
    log.debug(&quot;删除缓冲中的用户信息&quot;+userId);
    redisTemplate.delete(RedisKey.USERINFO+userId.toString());
}
</code></pre>

<p>6、只给出jwt的包，springboot结合security的包idea可以一键导入</p>

<pre><code class="language-xml">&lt;dependency&gt;
    &lt;groupId&gt;io.jsonwebtoken&lt;/groupId&gt;
    &lt;artifactId&gt;jjwt&lt;/artifactId&gt;
    &lt;version&gt;0.9.0&lt;/version&gt;
&lt;/dependency&gt;
</code></pre>

<p>7、AccountRoleRelation角色用户实体类</p>

<pre><code class="language-java">@Table(name = &quot;tb_account_role_relation&quot;)
@Entity
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class AccountRoleRelation extends BaseEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;)
    private Long id;

    @Column(name = &quot;role_id&quot;)
    private Long roleId;

    @Column(name = &quot;account_id&quot;)
    private Long accountId;

    @Column(name = &quot;is_delete&quot;)
    private Byte isDelete;

}
</code></pre>

<p>8、Role角色实体类</p>

<pre><code class="language-java">@Entity
@Table(name = &quot;tb_role&quot;)
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Role extends BaseEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;)
    private Long id;

    @Column(name = &quot;role_name&quot;)
    private String roleName;

    @Column(name = &quot;role_code&quot;)
    private String roleCode;

    @Column(name = &quot;sort&quot;)
    private Byte sort;

    @Column(name = &quot;role_introduction&quot;)
    private String roleIntroduction;

    @Column(name = &quot;is_delete&quot;)
    private Byte isDelete;

    @ManyToMany(fetch = FetchType.EAGER, targetEntity = Permission.class)
    @JoinTable(name = &quot;tb_role_permission_relation&quot;,joinColumns = @JoinColumn(name = &quot;role_id&quot;),inverseJoinColumns = @JoinColumn(name = &quot;permission_id&quot;))
    private Set&lt;Permission&gt; permissions = new LinkedHashSet&lt;&gt;();

}
</code></pre>

<p>9、RolePermissionRelation 角色权限实体类</p>

<pre><code class="language-java">@Entity
@Table(name = &quot;tb_role_permission_relation&quot;)
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class RolePermissionRelation extends BaseEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;)
    private Long id;

    @Column(name = &quot;permission_id&quot;)
    private Long permissionId;

    @Column(name = &quot;role_id&quot;)
    private Long roleId;

    @Column(name = &quot;is_delete&quot;)
    private Byte isDelete;
}
</code></pre>

<p>10、Permission权限实体类</p>

<pre><code class="language-java">@Entity
@Table(name = &quot;tb_permission&quot;)
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Permission extends BaseEntity implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = &quot;id&quot;)
    private Long id;

    @Column(name = &quot;permission_name&quot;)
    private String permissionName;

    @Column(name = &quot;permission_introduction&quot;)
    private String permissionIntroduction;

    @Column(name = &quot;permission_url&quot;)
    private String url;

    @Column(name = &quot;pid&quot;)
    private Long pid;

    @Column(name = &quot;is_delete&quot;)
    private Byte isDelete;
}
</code></pre>

                        </div>
						 
                        
<div class="post-archive">
    <ul class="post-copyright">
        <li><strong>原文作者：</strong><a rel="author" href="http://linxianqin.gitee.io">林贤钦</a></li>
        <li style="word-break:break-all"><strong>原文链接：</strong><a href="http://linxianqin.gitee.io/post/%E6%B5%85%E8%B0%88springboot-Security-Jwt-%E5%AE%9E%E7%8E%B0token/">http://linxianqin.gitee.io/post/%E6%B5%85%E8%B0%88springboot-Security-Jwt-%E5%AE%9E%E7%8E%B0token/</a></li>
        <li><strong>版权声明：</strong>本作品采用<a rel="license" href="https://linxianqin.gitee.io">知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议</a>进行许可，非商业转载请注明出处（作者，原文链接），商业转载请联系作者获得授权。</li>
    </ul>
</div>
<br/>



                        

<div class="post-archive">
    <h2>See Also</h2>
    <ul class="listing">
        
        <li><a href="/post/springSecurity%E7%9A%84session%E7%AE%A1%E7%90%86%E5%8F%8A%E5%AE%89%E5%85%A8%E9%85%8D%E7%BD%AE/">SpringSecurity的session管理及安全配置</a></li>
        
        <li><a href="/post/springSecurity%E8%87%AA%E5%AE%9A%E4%B9%89%E8%AE%A4%E8%AF%81%E7%BB%93%E6%9E%9C%E5%A4%84%E7%90%86/">SpringSecurity自定义认证结果处理</a></li>
        
        <li><a href="/post/springSecurity%E4%B8%ADformLogin%E7%99%BB%E5%BD%95%E8%AE%A4%E8%AF%81%E6%A8%A1%E5%BC%8F/">SpringSecurity中formLogin登录认证模式</a></li>
        
        <li><a href="/post/java8%E6%96%B0%E7%89%B9%E6%80%A7Lambda%E8%A1%A8%E8%BE%BE%E5%BC%8F/">Java8新特性Lambda表达式</a></li>
        
        <li><a href="/post/java8%E6%96%B0%E7%89%B9%E6%80%A7stream%E6%B5%81/">Java8新特性stream流</a></li>
        
    </ul>
</div>


                        <div class="post-meta meta-tags">
                            
                            <ul class="clearfix">
                                
                                <li><a href="http://linxianqin.gitee.io/tags/spring">spring</a></li>
                                
                                <li><a href="http://linxianqin.gitee.io/tags/java">java</a></li>
                                
                            </ul>
                            
                        </div>
                    </article>
                    
    

    
    
    <div class="post bg-white">
      <script src="https://utteranc.es/client.js"
            repo= "757610938/hugo-blogs-utterances"
            issue-term="pathname"
            theme="github-light"
            crossorigin="anonymous"
            async>
      </script>
    </div>
    
                </div>
            </div>
            <div id="secondary">
    <section class="widget">
        <form id="search" action="//www.google.com/search" method="get" accept-charset="utf-8" target="_blank" _lpchecked="1">
      
      <input type="text" name="q" maxlength="20" placeholder="Search">
      <input type="hidden" name="sitesearch" value="http://linxianqin.gitee.io">
      <button type="submit" class="submit icon-search"></button>
</form>
    </section>
    
    <section class="widget">
        <h3 class="widget-title">最近文章</h3>
<ul class="widget-list">
    
    <li>
        <a href="http://linxianqin.gitee.io/post/java-%E5%8F%8D%E5%B0%84/" title="Java 反射">Java 反射</a>
    </li>
    
    <li>
        <a href="http://linxianqin.gitee.io/post/java-%E9%93%BE%E8%A1%A8/" title="Java 链表">Java 链表</a>
    </li>
    
    <li>
        <a href="http://linxianqin.gitee.io/post/%E7%9F%AD%E4%BF%A1%E6%B3%A8%E5%86%8C%E5%92%8C%E5%BE%AE%E4%BF%A1%E7%99%BB%E5%BD%95%E7%9A%84%E5%AE%9E%E7%8E%B0/" title="短信注册和微信登录的实现">短信注册和微信登录的实现</a>
    </li>
    
    <li>
        <a href="http://linxianqin.gitee.io/post/OAuth2.0%E8%AE%A4%E8%AF%81%E6%8E%88%E6%9D%83%E5%8D%95%E7%82%B9%E7%99%BB%E5%BD%95/" title="OAuth2.0认证授权单点登录">OAuth2.0认证授权单点登录</a>
    </li>
    
    <li>
        <a href="http://linxianqin.gitee.io/post/Nacos%E6%9C%8D%E5%8A%A1%E6%B3%A8%E5%86%8C%E4%B8%AD%E5%BF%83/" title="Nacos服务注册中心">Nacos服务注册中心</a>
    </li>
    
    <li>
        <a href="http://linxianqin.gitee.io/post/SpringCloud%E5%BE%AE%E6%9C%8D%E5%8A%A1/" title="SpringCloud微服务">SpringCloud微服务</a>
    </li>
    
    <li>
        <a href="http://linxianqin.gitee.io/post/%E9%98%BF%E9%87%8C%E4%BA%91%E8%A7%86%E9%A2%91%E7%82%B9%E6%92%AD%E6%9C%8D%E5%8A%A1/" title="阿里云视频点播服务前后端实现">阿里云视频点播服务前后端实现</a>
    </li>
    
    <li>
        <a href="http://linxianqin.gitee.io/post/%E9%98%9F%E5%88%97/" title="队列">队列</a>
    </li>
    
    <li>
        <a href="http://linxianqin.gitee.io/post/%E7%A8%80%E7%96%8F%E6%95%B0%E7%BB%84/" title="稀疏数组">稀疏数组</a>
    </li>
    
    <li>
        <a href="http://linxianqin.gitee.io/post/%E9%98%BF%E9%87%8C%E4%BA%91EasyExcel%E5%AE%9E%E7%8E%B0%E5%90%8E%E7%AB%AF%E5%AF%B9Excel%E7%9A%84%E8%AF%BB%E5%86%99%E6%93%8D%E4%BD%9C/" title="使用阿里云EasyExcel实现后端对Excel的读写操作">使用阿里云EasyExcel实现后端对Excel的读写操作</a>
    </li>
    
</ul>
    </section>

    

    <section class="widget">
        <h3 class="widget-title">分类</h3>
<ul class="widget-list">
    
    <li><a href="http://linxianqin.gitee.io/categories/JWT/">JWT (2)</a></li>
    
    <li><a href="http://linxianqin.gitee.io/categories/hugo/">hugo (2)</a></li>
    
    <li><a href="http://linxianqin.gitee.io/categories/java/">java (18)</a></li>
    
    <li><a href="http://linxianqin.gitee.io/categories/java8/">java8 (1)</a></li>
    
    <li><a href="http://linxianqin.gitee.io/categories/springCloud/">springCloud (2)</a></li>
    
    <li><a href="http://linxianqin.gitee.io/categories/springSecurity/">springSecurity (4)</a></li>
    
    <li><a href="http://linxianqin.gitee.io/categories/vue/">vue (5)</a></li>
    
    <li><a href="http://linxianqin.gitee.io/categories/yml/">yml (1)</a></li>
    
    <li><a href="http://linxianqin.gitee.io/categories/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/">数据结构 (3)</a></li>
    
    <li><a href="http://linxianqin.gitee.io/categories/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95/">软件测试 (1)</a></li>
    
    <li><a href="http://linxianqin.gitee.io/categories/%E9%98%BF%E9%87%8C%E4%BA%91/">阿里云 (4)</a></li>
    
</ul>
    </section>

    <section class="widget">
        <h3 class="widget-title">标签</h3>
<div class="tagcloud">
    
    <a href="http://linxianqin.gitee.io/tags/JWT/">JWT</a>
    
    <a href="http://linxianqin.gitee.io/tags/hugo/">hugo</a>
    
    <a href="http://linxianqin.gitee.io/tags/java/">java</a>
    
    <a href="http://linxianqin.gitee.io/tags/java8/">java8</a>
    
    <a href="http://linxianqin.gitee.io/tags/spring/">spring</a>
    
    <a href="http://linxianqin.gitee.io/tags/springBoot/">springBoot</a>
    
    <a href="http://linxianqin.gitee.io/tags/springCloud/">springCloud</a>
    
    <a href="http://linxianqin.gitee.io/tags/ssm/">ssm</a>
    
    <a href="http://linxianqin.gitee.io/tags/vue/">vue</a>
    
    <a href="http://linxianqin.gitee.io/tags/yml/">yml</a>
    
    <a href="http://linxianqin.gitee.io/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/">数据结构</a>
    
    <a href="http://linxianqin.gitee.io/tags/%E8%BD%AF%E4%BB%B6%E6%B5%8B%E8%AF%95/">软件测试</a>
    
    <a href="http://linxianqin.gitee.io/tags/%E9%98%BF%E9%87%8C%E4%BA%91/">阿里云</a>
    
</div>
    </section>

    
<section class="widget">
    <h3 class="widget-title">友情链接</h3>
    <ul class="widget-list">
        
        <li>
            <a target="_blank" href="https://www.cnki.net" title="中国知网">中国知网</a>
        </li>
        
        <li>
            <a target="_blank" href="https://linxianqin.com" title="个人的另一个博客网站(国内加载慢)">个人的另一个博客网站(国内加载慢)</a>
        </li>
        
    </ul>
</section>


    <section class="widget">
        <h3 class="widget-title">其它</h3>
        <ul class="widget-list">
            <li><a href="http://linxianqin.gitee.io/index.xml">文章 RSS</a></li>
        </ul>
    </section>
</div>
        </div>
    </div>
</div>
<footer id="footer">
    <div class="container">
        &copy; 2020 <a href="http://linxianqin.gitee.io">林贤钦 By 林贤钦</a>.
        Powered by <a rel="nofollow noreferer noopener" href="https://gohugo.io" target="_blank">Hugo</a>.
        <a href="https://www.flysnow.org/" target="_blank">Theme</a> based on <a href="https://github.com/rujews/maupassant-hugo" target="_blank">maupassant</a>.
        
    </div>
</footer>


    
    <script type="text/javascript">
        
        (function () {
            $("pre code").parent().addClass("line-numbers")
        }());

        window.MathJax = {
            tex2jax: {
                inlineMath: [['$', '$']],
                processEscapes: true
                }
            };
    </script>
    <script type="text/javascript" src="/js/prism.js" async="true"></script>
    <script src='https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-MML-AM_CHTML' async></script>


<a id="rocket" href="#top"></a>
<script type="text/javascript" src="/js/totop.js?v=0.0.0" async=""></script>

<script type="application/javascript">
var doNotTrack = false;
if (!doNotTrack) {
	window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
	ga('create', 'GA ID', 'auto');
	
	ga('send', 'pageview');
}
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>



    <script type="text/javascript" src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" async></script>




</body>
</html>
